package org.apache.lucene.ocean.snapshotlog;

import java.io.EOFException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.lucene.ocean.LogDirectory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Log file for snapshot infos. It is human readable using XML.  Each entry 
 * is prepended with the length in bytes.  This allows the entry to be skipped
 * and not parsed on recovery.
 * 
 */
public class SnapshotLogFile {
  final static Logger LOG = LoggerFactory.getLogger(SnapshotLogFile.class);
  private long currentWritePosition = 0;
  private RandomAccessFile output;
  private String file;
  private LogDirectory logDirectory;
  private List<InfoHeader> infoHeaders = new ArrayList<InfoHeader>(); // sorted
                                                                      // descending
                                                                      // index
  private Long id;
  private int size = 0;
  private ReentrantLock writeLock = new ReentrantLock();
  
  
  public SnapshotLogFile(Long id, String file, LogDirectory logDirectory, int numHeadersOnHand) throws IOException {
    this.id = id;
    this.file = file;
    this.logDirectory = logDirectory;
    output = logDirectory.getOutput(file, false);
    currentWritePosition = logDirectory.fileLength(file);
    loadLastInfos(numHeadersOnHand);
  }
  
  /**
   * Number of entries in the file
   * @return
   */
  public int getSize() {
    return size;
  }
  
  /**
   * Close the file
   * @throws IOException
   */
  public void close() throws IOException {
    output.close();
  }
  
  /**
   * Delete the file
   * @throws IOException
   */
  public void delete() throws IOException {
    output.close();
    boolean deleted = logDirectory.deleteFile(file);
  }
  
  /**
   * Length of the file
   * @return
   * @throws IOException
   */
  public long length() throws IOException {
    return output.length();
  }

  public Long getId() {
    return id;
  }
  
  /**
   * Header of an entry.  Used to lookup an entry later on.
   */
  public static class InfoHeader {
    public final Long logId;
    public final long position;
    public final int length;
    public final int index;

    public InfoHeader(Long logId, int index, long position, int length) {
      this.logId = logId;
      this.index = index;
      this.position = position;
      this.length = length;
    }
  }
  
  /**
   * Write an entry as a string
   * @param string
   * @return
   * @throws IOException
   */
  public InfoHeader write(String string) throws IOException {
    writeLock.lock();
    try {
      int index = 0;
      if (infoHeaders.size() > 0) {
        index = infoHeaders.get(0).index + 1;
      }
      byte[] bytes = string.getBytes("UTF-8");
      output.seek(currentWritePosition);
      output.writeBytes(Integer.toString(bytes.length));
      output.write('\n');
      long position = output.getFilePointer();
      output.write(bytes);
      output.getFD().sync();
      currentWritePosition = output.getFilePointer();
      InfoHeader infoHeader = new InfoHeader(id, index, position, bytes.length);
      infoHeaders.add(0, infoHeader);
      size++;
      return infoHeader;
    } finally {
      writeLock.unlock();
    }
  }
  
  /**
   * Load the string from the given infoHeader
   * @param infoHeader
   * @return
   * @throws IOException
   */
  public String loadData(InfoHeader infoHeader) throws IOException {
    RandomAccessFile randomAccessFile = logDirectory.openInput(file);
    try {
      randomAccessFile.seek(infoHeader.position);
      byte[] bytes = new byte[infoHeader.length];
      randomAccessFile.readFully(bytes);
      return new String(bytes, "UTF-8");
    } finally {
      randomAccessFile.close();
    }
  }
  
  /**
   * Get the latest InfoHeaders 
   * @param n How many info headers to obtain
   * @return
   */
  public List<InfoHeader> getLastInfos(int n) {
    List<InfoHeader> list = new ArrayList<InfoHeader>(n);
    Iterator<InfoHeader> iterator = infoHeaders.iterator();
    for (int x = 0; x < n; x++) {
      if (iterator.hasNext()) {
        list.add(iterator.next());
      } else
        break;
    }
    return list;
  }
  
  private String readLine(RandomAccessFile randomAccessFile) throws IOException {
    StringBuilder builder = new StringBuilder(5);
    while (true) {
      byte b = randomAccessFile.readByte();
      if (b == '\n') {
        break;
      }
      builder.append((char)b);
    }
    return builder.toString();
  }
  
  /**
   * Loads only the last n snapshotinfos
   * 
   * @param n
   *          number of last infos to load
   * @throws IOException
   */
  public void loadLastInfos(int n) throws IOException {
    int index = 0;
    RandomAccessFile randomAccessFile = logDirectory.openInput(file);
    try {
      while (true) {
        try {
          //String lengthLine = randomAccessFile.readLine();
          String lengthLine = readLine(randomAccessFile);
          int length = Integer.parseInt(lengthLine);
          long position = randomAccessFile.getFilePointer();
          InfoHeader infoHeader = new InfoHeader(id, index, position, length);
          if (infoHeaders.size() > n) {
            // remove the last one
            infoHeaders.remove(infoHeaders.size() - 1);
          }
          // add the newest to the beginning
          infoHeaders.add(0, infoHeader);
          index++;
          size++;
          // skip to the next info
          randomAccessFile.seek(randomAccessFile.getFilePointer() + length);
        } catch (EOFException eofException) {
          break;
        }
      }
    } finally {
      randomAccessFile.close();
    }
  }
}
